<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 山峰绘制</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <!-- 使用script标签引用包含JSON数据的JavaScript文件 -->
    <script src="stockData.js"></script>
    <script src="hz300fd.js"></script>
    <script>
        // 全局配置常量
        const GRID_COLUMNS = 20; // 网格列数
        const GRID_ROWS = 20;    // 网格行数
        const SIZE_SCALE_FACTOR = 2; // 大小缩放因子
        const FIXED_CUBE_SIZE = 25 * SIZE_SCALE_FACTOR; // 固定立方体大小

        // 全局变量，用于存储涨跌幅数据
        let increaseData = {};
        // 当前排序方式，默认为按weight排序
        let currentSortMethod = 'weight'; // 'weight' 或 'increase'

        // 加载涨跌幅数据
        function loadIncreaseData() {
            try {
                // 将数据转换为以code为键的对象，便于快速查找
                window.hz300fd.forEach(item => {
                    increaseData[item.code] = item.increase;
                });
                console.log('成功加载涨跌幅数据:', increaseData);
            } catch (error) {
                console.error('加载涨跌幅数据失败:', error);
            }
        }
        // 初始化场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xE0E0E0); // 偏灰色背景色

        // 初始化相机，设置合理的远裁剪面以适应所有缩放级别
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
        // 调整相机位置
        camera.position.set(12, 20, 12); // 提高相机高度和距离，扩大视野
        camera.lookAt(0, 0, 0); // 相机看向原点

        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // 从JSON数据生成山峰
        function generateMountainsFromJSON(data) {
            // 定义颜色映射的配置
            const colorConfig = {
                // 上涨颜色范围：从灰白色到深红色
                increaseColors: [
                    new THREE.Color(0xD0D0D0), // 浅灰
                    new THREE.Color(0xFFCCCC), // 浅红
                    new THREE.Color(0xFF9999), // 中红
                    new THREE.Color(0xFF6666), // 较深红
                    new THREE.Color(0xFF3333), // 深红
                    new THREE.Color(0xCC0000)  // 暗红
                ],
                // 下跌颜色范围：从灰白色到深绿色
                decreaseColors: [
                    new THREE.Color(0xD0D0D0), // 浅灰
                    new THREE.Color(0xCCFFCC), // 浅绿
                    new THREE.Color(0x99FF99), // 中绿
                    new THREE.Color(0x66FF66), // 较深绿
                    new THREE.Color(0x33FF33), // 深绿
                    new THREE.Color(0x00CC00)  // 暗绿
                ],
                // 涨幅阈值范围（用于颜色映射）
                maxAbsIncrease: 0.05, // 假设最大涨跌幅为5%
                // 计算涨跌幅对应的颜色
                getColor: function (increase) {
                    if (Math.abs(increase) < 0.001) { // 接近0的值显示为灰白色
                        return new THREE.Color(0xD0D0D0);
                    }

                    let colors, progress;
                    if (increase > 0) {
                        // 上涨：使用红色系渐变
                        colors = this.increaseColors;
                        // 计算进度，限制在合理范围内
                        progress = Math.min(increase / this.maxAbsIncrease, 1);
                    } else {
                        // 下跌：使用绿色系渐变
                        colors = this.decreaseColors;
                        // 计算进度，限制在合理范围内
                        progress = Math.min(Math.abs(increase) / this.maxAbsIncrease, 1);
                    }

                    // 计算颜色索引和混合比例
                    const colorIndex = Math.min(
                        Math.floor(progress * (colors.length - 1)),
                        colors.length - 2
                    );
                    const blendFactor = progress * (colors.length - 1) - colorIndex;

                    // 线性插值计算最终颜色
                    const finalColor = new THREE.Color();
                    finalColor.lerpColors(
                        colors[colorIndex],
                        colors[colorIndex + 1],
                        blendFactor
                    );

                    return finalColor;
                }
            };

            // 存储所有山峰及其信息
            const mountainData = [];

            // 计算所有weight的总和
            const totalWeight = data.reduce((sum, item) => sum + item.weight, 0);

            // 为按照大小紧密排列准备数据
            data.forEach((item, index) => {
                // 调整weight，使总和为100
                const adjustedWeight = (item.weight / totalWeight) * 100;
                // 使用调整后的weight作为体积
                const volume = adjustedWeight;
                mountainData.push({
                    volume: volume, // 保存调整后的体积值
                    weight: item.weight,
                    name: item.name,
                    code: item.code
                });
            });

            // 根据当前选择的排序方式进行排序
            if (currentSortMethod === 'weight') {
                // 按weight属性排序，weight大的在前
                mountainData.sort((a, b) => {
                    //return b.weight - a.weight;
                    return a.weight - b.weight;
                });
            } else {
                // 按涨跌幅排序，涨幅大的在前
                mountainData.sort((a, b) => {
                    const incA = increaseData[a.code] || 0;
                    const incB = increaseData[b.code] || 0;
                    //return incB - incA;
                    return incA - incB;
                });
            }

            // 计算体积范围用于颜色映射
            const volumes = mountainData.map(m => m.volume);
            const minVolume = Math.min(...volumes);
            const maxVolume = Math.max(...volumes);
            const volumeRange = maxVolume - minVolume || 1;

            // 为每个山峰创建几何体和材质，并设置颜色
            mountainData.forEach((item, index) => {
                const volume = item.volume;
                const size = Math.sqrt(volume) * 150;

                // 颜色将在材质创建时基于涨跌幅设置

                // 设置高度放大倍数，用于等比例放大所有立方体高度
                const heightScaleFactor = 1; // 高度放大3倍
                // 设置长宽放大倍数，用于等比例放大所有立方体长宽
                const sizeScaleFactor = 1; // 长宽放大2倍

                // 根据体积计算立方体的尺寸
                const width = size * 0.8 * sizeScaleFactor; // 宽度稍小于原始大小并乘以放大倍数
                const depth = size * 0.8 * sizeScaleFactor; // 深度稍小于原始大小并乘以放大倍数
                const height = volume * 50 * heightScaleFactor; // 高度基于体积并乘以放大倍数

                // 使用BoxBufferGeometry创建立方体
                const geometry = new THREE.BoxBufferGeometry(width, height, depth);

                // 根据code匹配涨跌幅数据，设置颜色
                let cubeColor = new THREE.Color(0xD0D0D0); // 默认灰白色（接近0）
                if (increaseData[item.code] !== undefined) {
                    console.log('涨跌幅数据:', increaseData[item.code]);
                    // 使用颜色配置对象计算对应的渐变颜色
                    cubeColor = colorConfig.getColor(increaseData[item.code]);
                }

                // 创建材质，增加边框效果提高立方体之间的区分度
                const material = new THREE.MeshPhongMaterial({
                    color: cubeColor,
                    shininess: 60, // 增加光泽度
                    emissive: cubeColor.multiplyScalar(0.2), // 添加适当的自发光效果
                    wireframe: false,
                    side: THREE.FrontSide,
                    transparent: false
                });

                // 创建网格
                const mountain = new THREE.Mesh(geometry, material);
                // 设置立方体位置，使其底部与地面接触
                mountain.position.y = height / 2;

                // 更新山峰数据
                item.mountain = mountain;
            });

            // 根据当前选择的排序方式进行排序
            if (currentSortMethod === 'weight') {
                // 按weight属性排序，weight大的在前
                mountainData.sort((a, b) => {
                    //return b.weight - a.weight;
                    return a.weight - b.weight;
                });
            } else {
                // 按涨跌幅排序，涨幅大的在前
                mountainData.sort((a, b) => {
                    const incA = increaseData[a.code] || 0;
                    const incB = increaseData[b.code] || 0;
                    //return incB - incA;
                    return incA - incB;
                });
            }

            // 设置每个立方体的位置并添加到场景
            const mountains = [];

            // 显示所有立方体
            const displayCount = mountainData.length;

            // 设置固定大小的立方体，以便排满画布
            const sizeScaleFactor = 2; // 长宽放大2倍
            let fixedCubeSize = 40 * sizeScaleFactor; // 固定立方体大小，乘以放大倍数（使用let以便后续修改）

            
            // 计算总宽度和总深度
            const totalWidth = GRID_COLUMNS * FIXED_CUBE_SIZE;
            const totalDepth = GRID_ROWS * FIXED_CUBE_SIZE;

            // 为每个立方体设置固定大小
            mountainData.forEach((item, index) => {
                const increase = increaseData[item.code] || 0;
                item.mountain.name = `${item.name} (${item.code}) - 体积: ${item.volume.toFixed(2)} - 涨跌幅: ${(increase * 100).toFixed(2)}%`; // 设置名称，包含代码、体积和涨跌幅

                // 使用科学缩放方法，基于weight属性计算立方体高度
                // 1. 获取原始weight值
                const rawWeight = item.weight;

                // 2. 计算所有weight的最大值和最小值，用于归一化
                const allWeights = mountainData.map(m => m.weight);
                const minWeight = Math.min(...allWeights);
                const maxWeight = Math.max(...allWeights);
                const weightRange = maxWeight - minWeight;

                // 3. 科学缩放 - 使用对数缩放避免高度差异过大或过小
                // 先归一化到[0,1]范围，然后应用对数变换
                const normalizedWeight = weightRange > 0 ? (rawWeight - minWeight) / weightRange : 0.5;

                // 4. 使用对数变换使高度差异更加明显但不过度
                // Math.log1p(normalizedWeight * 10) 确保即使是小值也有一定的高度差异
                const scaledWeight = Math.log1p(normalizedWeight * 10);

                // 5. 设置高度参数
                const baseHeight = 150; // 基础高度
                const heightScaleFactor = 25; // 高度缩放因子
                const minHeight = 50; // 最小高度，确保最小的立方体也能看清

                // 6. 计算最终高度
                const calculatedHeight = baseHeight + scaledWeight * heightScaleFactor;
                const height = Math.max(minHeight, calculatedHeight);

                // 重新创建立方体几何体以使用固定大小
                const newGeometry = new THREE.BoxBufferGeometry(FIXED_CUBE_SIZE * 0.9, height, FIXED_CUBE_SIZE * 0.9); // 略微减小立方体尺寸，增加间距效果
                item.mountain.geometry.dispose(); // 释放旧几何体
                item.mountain.geometry = newGeometry; // 设置新几何体
                item.mountain.position.y = height / 2; // 重置Y位置

                // 为立方体添加边框，增强区分度
                const edges = new THREE.EdgesGeometry(item.mountain.geometry);
                const edgeMaterial = new THREE.LineBasicMaterial({
                    color: 0x333333, // 深色边框
                    linewidth: 1
                });
                const edgeMesh = new THREE.LineSegments(edges, edgeMaterial);
                item.mountain.add(edgeMesh); // 将边框添加到立方体上

                // 在立方体顶面添加名称和涨跌幅文本
                const itemIncrease = increaseData[item.code] || 0;
                addTextToCube(item.mountain, item.name, itemIncrease, height, item.mountain.material.color, fixedCubeSize);

                // 为立方体的四个侧面添加文字
                const sides = ['front', 'back', 'left', 'right'];
                sides.forEach(side => {
                    addSideTextToCube(item.mountain, item.name, item.code, height, item.mountain.material.color, side);
                });
            });

            // 计算起始位置，使整个矩形居中，确保立方体不会超出网格范围
            const startX = -totalWidth / 2;
            const startZ = -totalDepth / 2;

            // 按照大小顺序在网格内排列所有立方体，类似换行显示
            for (let i = 0; i < displayCount; i++) {
                const item = mountainData[i];

                // 计算网格坐标（类似表格布局，从左到右，从上到下）
                const col = i % GRID_COLUMNS;
                const row = Math.floor(i / GRID_COLUMNS);

                // 计算实际位置，确保立方体紧密排列、充满画布且不超出网格范围
                const positionX = startX + col * FIXED_CUBE_SIZE + FIXED_CUBE_SIZE / 2;
                const positionZ = startZ + row * FIXED_CUBE_SIZE + FIXED_CUBE_SIZE / 2;

                // 设置立方体位置，保持正确的y轴位置
                item.mountain.position.set(positionX, item.mountain.position.y, positionZ);

                scene.add(item.mountain);
                mountains.push(item.mountain);
            }

            return mountains;
        }

        // 创建射线投射器，用于检测鼠标是否悬停在山峰上
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();

        // 创建显示名字的div元素
        const tooltip = document.createElement('div');
        tooltip.className = 'tooltip';
        document.body.appendChild(tooltip);

        // 最近悬停的山峰对象
        let hoveredMountain = null;

        // 更新鼠标位置
        function updateMousePosition(event) {
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        }

        // 检测鼠标悬停
        function detectHoveredMountain(event) {
            updateMousePosition(event);

            // 更新射线投射器
            raycaster.setFromCamera(mouse, camera);

            // 计算与所有山峰的交点
            const intersects = raycaster.intersectObjects(mountains || []);

            // 如果有交点
            if (intersects.length > 0) {
                const intersect = intersects[0];
                const mountain = intersect.object;

                // 如果悬停在新的山峰上
                if (mountain !== hoveredMountain) {
                    hoveredMountain = mountain;
                    tooltip.textContent = mountain.name;
                    tooltip.style.left = event.clientX + 10 + 'px';
                    tooltip.style.top = event.clientY - 30 + 'px';
                    tooltip.style.display = 'block';
                } else {
                    // 更新tooltip位置
                    tooltip.style.left = event.clientX + 10 + 'px';
                    tooltip.style.top = event.clientY - 30 + 'px';
                }
            } else {
                // 没有悬停在任何山峰上
                if (hoveredMountain !== null) {
                    hoveredMountain = null;
                    tooltip.style.display = 'none';
                }
            }
        }

        // 添加鼠标移动事件监听器
        document.addEventListener('mousemove', detectHoveredMountain);

        // 初始相机位置
        camera.position.set(0, 300, 300); // 默认适中的相机位置
        camera.lookAt(0, 0, 0);

        // 添加更强的光源，提高整体对比度
        const ambientLight = new THREE.AmbientLight(0x808080); // 增强环境光
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); // 增强主光源
        directionalLight.position.set(2, 3, 2); // 调整光源位置
        scene.add(directionalLight);

        // 添加辅助光源，提高立体感
        const fillLight = new THREE.DirectionalLight(0xffffff, 0.6);
        fillLight.position.set(-3, 2, -1);
        scene.add(fillLight);

        // 添加坐标轴辅助，使用默认颜色但增加长度以提高可见性
        const axesHelper = new THREE.AxesHelper(600); // 增加长度提高可见性
        scene.add(axesHelper);

        // 添加网格辅助，调整颜色提高对比度
        // 先创建默认的网格辅助，避免undefined错误
        let gridHelper = new THREE.GridHelper(1000, 20, 0x333355, 0x222244); // 深蓝色网格，与背景协调
        scene.add(gridHelper);

        // 声明全局mountains变量
        let mountains = [];

        // 为立方体添加顶面文字
        function addTextToCube(cube, name, increase, cubeHeight, cubeColor, cubeSize) {
            // 创建文字材质 - 强制使用高对比度颜色以确保可见性
            const textColor = 0xFFFFFF; // 始终使用白色文字，确保在各种背景上都可见 // 如果立方体颜色较浅，使用黑色文字；否则使用白色文字

            // 创建Canvas作为文字纹理
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');

            // 设置字体大小
            const fontSize = 14;
            const increaseFontSize = 12;
            context.font = `${fontSize}px Arial`;

            // 限制名称长度
            const maxNameLength = 10;
            let displayName = name;
            if (name.length > maxNameLength) {
                displayName = name.substring(0, maxNameLength) + '...';
            }

            // 格式化涨跌幅为百分比，保留两位小数
            const increasePercent = (increase * 100).toFixed(2) + '%';

            // 测量文本宽度和高度
            const nameWidth = context.measureText(displayName).width + 10;
            context.font = `${increaseFontSize}px Arial`;
            const increaseWidth = context.measureText(increasePercent).width + 10;

            const textWidth = Math.max(nameWidth, increaseWidth);
            const textHeight = (fontSize + increaseFontSize + 12); // 两行文字高度加上间距

            // 设置canvas尺寸
            canvas.width = textWidth;
            canvas.height = textHeight;

            // 重新设置字体
            context.font = `${fontSize}px Arial`;

            // 设置文本对齐方式
            context.textAlign = 'center';
            context.textBaseline = 'middle';

            // 强制使用白色文字以确保最高可见性
            context.fillStyle = 'rgba(255, 255, 255, 1)';

            // 添加黑色描边以提高对比度和可读性
            context.strokeStyle = 'rgba(0, 0, 0, 1)';
            context.lineWidth = 3; // 增加描边宽度以提高文字清晰度

            // 添加阴影效果以增强文字的立体感
            context.shadowColor = 'rgba(0, 0, 0, 0.8)';
            context.shadowBlur = 4;
            context.shadowOffsetX = 1;
            context.shadowOffsetY = 1;

            // 绘制第一行：名称
            context.fillText(displayName, textWidth / 2, fontSize + 2);

            // 绘制第二行：涨跌幅
            context.font = `${increaseFontSize}px Arial`;
            context.fillText(increasePercent, textWidth / 2, fontSize + increaseFontSize + 8);

            // 创建纹理
            const texture = new THREE.CanvasTexture(canvas);
            texture.needsUpdate = true;

            // 创建文字材质
            const textMaterial = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide
            });

            // 创建立方体顶面大小的平面几何体
            const textGeometry = new THREE.PlaneGeometry(FIXED_CUBE_SIZE * 0.8, FIXED_CUBE_SIZE * 0.4); // 文字板的大小

            // 创建文字网格
            const textMesh = new THREE.Mesh(textGeometry, textMaterial);

            // 设置文字位置在立方体顶面中心
            textMesh.position.set(0, cubeHeight / 2 + 0.1, 0);

            // 使用负角度旋转，确保从顶部观看时文字不被镜像
            textMesh.rotation.x = -Math.PI / 2;

            // 将文字添加到立方体
            cube.add(textMesh);
        }

        // 为立方体侧面添加文字（name和code都竖向显示，且之间换行）
        function addSideTextToCube(cube, name, code, cubeHeight, cubeColor, side) {
            // 创建文字材质 - 使用相反色调以提高可读性
            const textColor = cubeColor.r > 0.5 ? 0x000000 : 0xFFFFFF;

            // 创建Canvas作为文字纹理
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');

            // 设置更大的字体大小，使用加粗字体以确保文字清晰可见
            const fontSize = 48;
            context.font = `900 ${fontSize}px Arial`; // 使用900来表示最粗的字体

            // 限制名称长度
            const maxNameLength = 6;
            let displayName = name;
            if (name.length > maxNameLength) {
                displayName = name.substring(0, maxNameLength) + '...';
            }

            // 测量文字高度，调整行高以优化显示
            const charHeight = fontSize;
            // 减小行高值，使竖向字符间距更加紧凑
            const lineHeight = charHeight;  // 减小行高，使字符间距更紧凑

            // 将name分割为单个字符，实现竖向显示（每个字符占一行）
            const nameLines = displayName.split(''); // name竖向显示，每个字符占一行

            // 计算总高度，只基于name
            const totalLines = nameLines.length;

            // 计算最大字符宽度，确保足够的显示空间
            let maxCharWidth = 0;
            [...displayName].forEach(char => {
                const charWidth = context.measureText(char).width;
                maxCharWidth = Math.max(maxCharWidth, charWidth);
            });

            // 设置紧凑的canvas尺寸，适应较小的行高
            const canvasWidth = maxCharWidth + 15; // 适中的左右边距
            const canvasHeight = totalLines * lineHeight + 50; // 适中的上下边距
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;

            // 设置文本对齐方式
            context.textAlign = 'center';
            context.textBaseline = 'middle';

            // 设置文字颜色
            context.fillStyle = `rgb(${((textColor >> 16) & 0xFF)}, ${((textColor >> 8) & 0xFF)}, ${(textColor & 0xFF)})`;

            // 绘制name（竖向显示，更紧凑排列）
            nameLines.forEach((char, index) => {
                // 使用紧凑的垂直位置计算，顶部留出少量边距  从顶部往下移动，避免文字被裁剪在顶
                const verticalPosition = 40 + index * lineHeight * 0.6;
                // 先绘制描边
                context.strokeText(char, canvasWidth / 2, verticalPosition);
                // 再绘制填充
                context.fillText(char, canvasWidth / 2, verticalPosition);
            });

            // 重置阴影设置，避免影响其他绘制
            context.shadowColor = 'transparent';
            context.shadowBlur = 0;
            context.shadowOffsetX = 0;
            context.shadowOffsetY = 0;

            // 创建纹理
            const texture = new THREE.CanvasTexture(canvas);
            texture.needsUpdate = true;

            // 创建文字材质
            const textMaterial = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide
            });

            // 创建更大的侧面文字平面几何体，确保文字更加清晰可见
            const textGeometry = new THREE.PlaneGeometry(40, cubeHeight * 0.9);

            // 创建文字网格
            const textMesh = new THREE.Mesh(textGeometry, textMaterial);

            // 保存初始旋转，用于后续更新
            textMesh.initialRotationY = 0;

            // 设置文字位置和旋转，根据侧面不同
            const offset = (FIXED_CUBE_SIZE / 2) + 8; // 更大的偏移，确保文字平面完全位于立方体外侧，并且更加突出

            switch (side) {
                case 'front': // Z轴正方向
                    textMesh.position.set(0, 0, offset);
                    textMesh.rotation.y = 0;
                    textMesh.initialRotationY = 0;
                    break;
                case 'back': // Z轴负方向
                    textMesh.position.set(0, 0, -offset);
                    textMesh.rotation.y = Math.PI;
                    textMesh.initialRotationY = Math.PI;
                    break;
                case 'left': // X轴负方向
                    textMesh.position.set(-offset, 0, 0);
                    textMesh.rotation.y = -Math.PI / 2;
                    textMesh.initialRotationY = -Math.PI / 2;
                    break;
                case 'right': // X轴正方向
                    textMesh.position.set(offset, 0, 0);
                    textMesh.rotation.y = Math.PI / 2;
                    textMesh.initialRotationY = Math.PI / 2;
                    break;
            }

            // 将文字添加到立方体
            cube.add(textMesh);
        }

        // 保持文字始终面向相机
        function updateTextOrientation() {
            mountains.forEach(mountain => {
                // 查找顶面文字网格
                const topTextMesh = mountain.children.find(child =>
                    child.geometry instanceof THREE.PlaneGeometry &&
                    Math.abs(child.rotation.x + Math.PI / 2) < 0.1
                );

                if (topTextMesh) {
                    // 复制相机的旋转
                    const textRotation = topTextMesh.rotation.clone();
                    const cameraRotation = camera.rotation.clone();

                    // 保持文字平面朝上，但跟随相机的水平旋转
                    textRotation.y = cameraRotation.y;
                    textRotation.z = 0; // 固定z轴旋转

                    // 应用旋转
                    topTextMesh.rotation.copy(textRotation);
                }

                // 查找侧面文字网格（不包括顶面文字）
                const sideTextMeshes = mountain.children.filter(child =>
                    child.geometry instanceof THREE.PlaneGeometry &&
                    Math.abs(child.rotation.x + Math.PI / 2) >= 0.1
                );

                sideTextMeshes.forEach(sideTextMesh => {
                    // 复制相机的旋转
                    const textRotation = sideTextMesh.rotation.clone();
                    const cameraRotation = camera.rotation.clone();

                    // 对于侧面文字，根据其初始旋转调整
                    // 主要调整Y轴旋转，保持文字面向相机
                    if (Math.abs(sideTextMesh.initialRotationY) < 0.1 ||
                        Math.abs(sideTextMesh.initialRotationY - Math.PI) < 0.1) {
                        // 前后侧面
                        textRotation.y = cameraRotation.y + (sideTextMesh.initialRotationY || 0);
                    } else {
                        // 左右侧面
                        textRotation.y = cameraRotation.y + (sideTextMesh.initialRotationY || 0);
                    }

                    // 固定X和Z轴旋转
                    textRotation.x = 0;
                    textRotation.z = 0;

                    // 应用旋转
                    sideTextMesh.rotation.copy(textRotation);
                });
            });
        }

        // 从全局变量加载JSON数据并生成山峰
        function loadDataAndGenerateMountains() {
            try {
                // 检查全局变量是否已加载
                if (window.stockData && window.stockData.length > 0) {
                    // 生成山峰
                    mountains = generateMountainsFromJSON(window.stockData);

                    console.log(`成功从全局变量加载并生成了${mountains.length}个山峰`);
                } else {
                    console.error('未找到有效的JSON数据');
                }
            } catch (error) {
                console.error('加载JSON数据时出错:', error);
            }

            // 动态创建或更新网格辅助，基于实际数据量
            if (gridHelper) {
                scene.remove(gridHelper);
            }

            // 使用全局配置的网格布局
            const totalWidth = GRID_COLUMNS * FIXED_CUBE_SIZE;
            const totalDepth = GRID_ROWS * FIXED_CUBE_SIZE;
            const maxDimension = Math.max(totalWidth, totalDepth);

            // 创建新的网格辅助，使用全局配置的网格参数，使用偏白色的网格线，确保清晰可见
            gridHelper = new THREE.GridHelper(maxDimension, GRID_COLUMNS, 0xCCCCCC, 0xAAAAAA); // 使用配置的网格线数量
            scene.add(gridHelper);
        }

        // 根据排序方式重新生成山峰
        function regenerateMountainsBySort() {
            // 清除现有山峰
            mountains.forEach(mountain => {
                scene.remove(mountain);
            });
            mountains = [];

            // 重新生成山峰
            loadDataAndGenerateMountains();
        }

        // 页面加载完成后执行
        window.addEventListener('load', async () => {
            // 先加载涨跌幅数据
            loadIncreaseData();
            // 然后生成box
            loadDataAndGenerateMountains();

            // 创建排序控件
            const sortControl = document.createElement('div');
            sortControl.className = 'sort-control';
            sortControl.innerHTML = `
                <label for="sort-method">排序方式：</label>
                <select id="sort-method">
                    <option value="weight">按权重</option>
                    <option value="increase">按涨跌幅</option>
                </select>
            `;
            document.body.appendChild(sortControl);

            // 添加排序方式变化的事件监听
            document.getElementById('sort-method').addEventListener('change', function () {
                currentSortMethod = this.value;
                regenerateMountainsBySort();
            });
        });

        // 手动实现鼠标拖动查看功能
        let isDragging = false;
        let previousMouseX = 0;
        let previousMouseY = 0;
        let rotateSpeed = 0.005; // 旋转速度
        let zoomSpeed = 0.1; // 缩放速度

        // 鼠标按下事件
        document.addEventListener('mousedown', (event) => {
            isDragging = true;
            previousMouseX = event.clientX;
            previousMouseY = event.clientY;
        });

        // 鼠标移动事件
        document.addEventListener('mousemove', (event) => {
            if (isDragging) {
                const deltaX = event.clientX - previousMouseX;
                const deltaY = event.clientY - previousMouseY;

                // 围绕Y轴旋转（左右拖动）
                scene.rotation.y += deltaX * rotateSpeed;

                // 围绕X轴旋转（上下拖动）
                scene.rotation.x += deltaY * rotateSpeed;

                // 限制X轴旋转范围，防止过度倾斜
                scene.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, scene.rotation.x));

                previousMouseX = event.clientX;
                previousMouseY = event.clientY;
            }
        });

        // 鼠标释放事件
        document.addEventListener('mouseup', () => {
            isDragging = false;
        });

        // 鼠标离开事件
        document.addEventListener('mouseleave', () => {
            isDragging = false;
        });

        // 优化的鼠标滚轮缩放功能
        document.addEventListener('wheel', (event) => {
            event.preventDefault(); // 阻止默认的滚动行为

            // 计算缩放方向和速度
            const delta = Math.sign(event.deltaY);

            // 根据当前相机位置动态调整缩放速度
            const dynamicZoomSpeed = Math.min(0.1, camera.position.distanceTo(new THREE.Vector3()) * 0.002);

            // 缩放时沿着视线方向移动相机
            const zoomFactor = 1 + delta * dynamicZoomSpeed;

            // 计算从相机到目标点(原点)的向量
            const direction = new THREE.Vector3().subVectors(new THREE.Vector3(), camera.position).normalize();

            // 沿着视线方向移动相机
            const newPosition = new THREE.Vector3().copy(camera.position)
                .addScaledVector(direction, camera.position.length() * (1 - zoomFactor));

            // 设置相机位置，限制最小和最大距离
            const minDistance = 200; // 最小距离，防止过于靠近
            const maxDistance = 2000; // 最大距离，防止过于远离

            if (newPosition.length() >= minDistance && newPosition.length() <= maxDistance) {
                camera.position.copy(newPosition);
                camera.lookAt(0, 0, 0); // 保持相机朝向原点
            }
        });

        // 渲染循环
        function animate() {
            requestAnimationFrame(animate);

            // 更新文字方向，使其始终面向相机
            updateTextOrientation();

            renderer.render(scene, camera);
        }

        animate();

        // 处理窗口大小变化
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>

</html>